package kandl.automation.junit;

import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.concurrent.Future;

import kandl.automation.junit.exception.FailedTestCaseException;
import kandl.automation.reporting.ResultReporter;
import kandl.automation.steprunner.StepRunner;
import kandl.automation.testsuite.TestCase;
import kandl.automation.testsuite.TestProfile;
import kandl.automation.testsuite.TestStep;
import kandl.automation.testsuite.TestSuite;

@Component
public class ProfileRunner {

    private boolean failFast;

    private String screenshotsBase;

    @Autowired
    private StepRunner stepRunner;

    @Autowired
    private ResultReporter resultReporter;

    private DescriptionMapper dMapper;

    public ProfileRunner() {
        this.failFast = Boolean.parseBoolean(System.getProperty("failfast", "true"));
        this.screenshotsBase = System.getProperty("screenshot.base", "");
    }

    public ProfileRunner(StepRunner stepRunner, ResultReporter resultReporter) {
        this();
        this.stepRunner = stepRunner;
        this.resultReporter = resultReporter;
    }

    @Async
    public Future<Void> runProfile(TestProfile profile, TestSuite suite, RunNotifier runNotifier, Description profileDescription) {

        for (TestCase testCase : suite.getTestCases(profile)) {
            String tcId = getUniqueIdForTestCase(testCase, profile);
            Description testCaseDescription = dMapper.getDescription(tcId, profileDescription);
            if (testCase.isEnabled()) {
                runTestCase(suite, profile, testCase, runNotifier, testCaseDescription);
            }
            else {
                runNotifier.fireTestIgnored(testCaseDescription);
            }
        }

        stepRunner.finish(profile);
        return new AsyncResult<Void>(null);
    }


    private void runTestCase(TestSuite suite, TestProfile profile, TestCase testCase, RunNotifier runNotifier, Description testDescription) {

        runNotifier.fireTestStarted(testDescription);

        FailedTestCaseException exception = new FailedTestCaseException(testCase, profile, suite);

        for (TestStep step : suite.getTestSteps(testCase)) {
            try {
                stepRunner.execute(step, profile);
            }
            catch (Exception e) {
                String screenshotPath = stepRunner.screenshot(step, profile);
                if (screenshotPath != null) {
                    screenshotPath = screenshotsBase + screenshotPath;
                }
                exception.addStep(step, e, screenshotPath);
                if (failFast) {
                    break;
                }
            }
        }

        if (exception.getSteps().isEmpty()) {
            resultReporter.pass(testCase, profile.getBrowser(), suite);
            runNotifier.fireTestFinished(testDescription);
        }
        else {
            resultReporter.fail(testCase, profile.getBrowser(), suite, exception.getMessage());
            runNotifier.fireTestFailure(new Failure(testDescription, exception));
        }
    }

    public void ignoreProfile(TestProfile profile, TestSuite suite, RunNotifier runNotifier, Description profileDescription) {
        for (TestCase testCase : suite.getTestCases(profile)) {
            String tcId = getUniqueIdForTestCase(testCase, profile);
            Description testCaseDescription = dMapper.getDescription(tcId, profileDescription);
            runNotifier.fireTestIgnored(testCaseDescription);
        }
    }

    public String getUniqueIdForTestCase(TestCase testCase, TestProfile profile) {
        return testCase.getId() + ", " + testCase.getDescription() + " (" + profile.getBrowser() + ")";
    }

    public void setDescriptionMapper(DescriptionMapper descriptionMapper) {
        this.dMapper = descriptionMapper;
    }

    public void setFailFast(boolean failFast) {
        this.failFast = failFast;
    }
}

